環境使用為Ruby 2.0.0 + Rails 4.0.0

請參考
https://github.com/plataformatec/devise#getting-started

這邊建議不要用sqllite,所以多加個
gem 'mysql2'

app/config/database.yml設定

app/config/database.yml
1
2
3
4
5
6
7
development:
adapter: mysql2
encoding: utf8
database: database_name
host: localhost
username: root
password:

Gemfile 加入

Gemfile
1
2
3
# Auth
gem 'devise'
gem 'omniauth-facebook'

接著bundle install

安裝Devise

$ rails g devise:install

建立Devise的User Model

$ rails g devise user
$ rake db:migrate

設定預設的URL選項


config/environments/development.rb
加入
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

產生view

$ rails g devise:views

調整Route

devise 會自動加入 devise_for :users
接著自己指定一個 root ,例如
root "application#index"

開一個view以後,
加入下列網址就有連結可以登入

app/views/applicatoin/index.html.erb
1
2
3
4
5
6
7
<% if user_signed_in? %>
<p>Welcome <%= current_user.email %></p>
<%= link_to 'Log out', destroy_user_session_path, :method => :delete %>
<% else %>
<p>You are not signed in.</p>
<%= link_to 'Login', new_user_session_path %>
<% end %>

複製進去就有登入介面了~


※ 有在用git可以備份一下

1
2
3
$ git init
$ git add .
$ git commit -m "add devise"

Devise 告一段落,接下來要接 Facebook啦

接著再參考
https://github.com/plataformatec/devise/wiki/OmniAuth%3A-Overview
http://blog.xdite.net/posts/2011/12/06/omniauth-clean-auth-provider-4

因為前面已經將gem放入了,就直接下一步

加入第三方的欄位到user表內產生 Authorization Model

1
2
$ rails g model Authorization provider:string user_id:integer uid:string
$ rake db:migrate

接著model設定個一對多

app/models/authorization.rb
1
2
3
class Authorization < ActiveRecord::Base
belongs_to :user
end
app/models/user.rb
1
2
3
4
5
6
7
8
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :authorizations
end

設定facebook資料

config/initializers/devise.rb加入

config/nitializers/devise.rb
1
2
config.omniauth :facebook, "APP_ID", "APP_SECRET"
`

若需要不同的授權如下

config/initializers/devise.rb
1
2
config.omniauth :facebook, "APP_ID", "APP_SECRET",
{:scope => 'email, offline_access'}

加入OmniAuth到User model
在 app/model/users.rb 的 devise後方加入 :omniauthable, :omniauth_providers => [:facebook] 也就是

app/model/users.rb
1
2
3
4
5
6
7
8
9
10
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable, :omniauth_providers => [:facebook]
has_many :authorizations
end

調整Route

將Route內的 devise_for: 加入 controller,也就是

routes.rb
1
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }

意思為只要收到的是 moniauth_callbacks型態 就指向 users controller的omniauth_callbacks method
可自行設定為

routes.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
devise_for :users, :controllers => {
:registrations => "registrations", #註冊頁
:omniauth_callbacks => "users/omniauth_callbacks" #接收redirect的controller
} do
get "logout" => "devise/sessions#destroy"
end
```
__redirect處理__
建立 `app/controllers/users/omniauth_callbacks_controller.rb`
這裡主要是 ** 接收redirect資訊取user資訊 **或是建立新使用者,並** 將其寫入session **來判斷會員狀態
``` ruby app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# You need to implement the method below in your model (e.g. app/models/user.rb)
@user = User.find_for_facebook_oauth(request.env["omniauth.auth"])
if @user.persisted?
sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end

接收到的格式可參考

https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema

大概長這樣

Auth hash schema
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{"provider"=>"facebook",
"uid"=>"1490623964507376",
"info"=>
{"email"=>"open_bkunfsm_user@tfbnw.net",
"name"=>"Open Graph Test User",
"first_name"=>"Open",
"last_name"=>"User",
"image"=>"http://graph.facebook.com/1490623964507376/picture",
"urls"=>
{"Facebook"=>
"https://www.facebook.com/app_scoped_user_id/1490623964507376/"},
"verified"=>false},
"credentials"=>
{"token"=>
"CAAHS4NieH9ABALBHSUAecP4MDHkgul2gn1gSnBsZBVjceF1GZB2x9Uv8VaYZBcxuwGlj
"expires_at"=>1408167744,
"expires"=>true},
"extra"=>
{"raw_info"=>
{"id"=>"1490623964507376",
"email"=>"open_bkunfsm_user@tfbnw.net",
"first_name"=>"Open",
"gender"=>"female",
"last_name"=>"User",
"link"=>"https://www.facebook.com/app_scoped_user_id/1490623964507376
"locale"=>"en_US",
"middle_name"=>"Graph Test",
"name"=>"Open Graph Test User",
"timezone"=>8,
"updated_time"=>"2014-06-15T06:29:28+0000",
"verified"=>false}}}

接著建立model去處理接收的provider資訊
app/models/user.rb 底下新增 find_for_facebook_oauth method,這個方法 omniauth_callbacks_controller 會呼叫。

app/models/user.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def self.find_for_facebook_oauth(auth)
# binding.pry
authentication = Authorization.where(auth.slice(:provider, :uid)).first
if authentication
user = authentication.user
else
user = User.new;
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
if user.save(:validate => false)
user.authorizations << Authorization.new(:provider => auth.provider, :uid => auth.uid )
user.save
user
else
Rails.logger.warn("User create fail, #{user.errors.inspect}")
end
end
end

補上登出

app/models/user.rb
1
2
3
4
5
6
7
def self.new_with_session(params, session)
super.tap do |user|
if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"]
user.email = data["email"] if user.email.blank?
end
end
end

OK,搞定,可以測試啦